package org.simplextensions.registry.services;

import org.simplextensions.registry.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 
 * @author Tomasz Krzyzak, <a
 *         href="mailto:tomasz.krzyzak@gmail.com">tomasz.krzyzak@gmail.com</a>
 * @since 2009-07-29 21:58:02
 * 
 */
@org.simplextensions.annotations.ExtensionPoint(id = ServiceRegistry.EP_ID, extensionClass = { IServiceLocator.class })
public class ServiceRegistry extends ExtensionPointAdapter implements IServiceRegistry {

	public static final String EP_ID = "ServiceRegistry";

	public static final String SERVICE_INTERFACE = "serviceInterface";
	public static final String EXTENSION_TYPE = "extensionType";
	public static final String EXTENSION_TYPE_SERVICE_LOCATOR = "serviceLocator";
	public static final String EXTENSION_TYPE_SERVICE = "service";

	private static final Logger log = LoggerFactory.getLogger(ServiceRegistry.class);

	private Map<Class<?>, Extension> extensionsMap = new HashMap<Class<?>, Extension>();

	private Map<Class<?>, Object> servicesMap = new HashMap<Class<?>, Object>();
	private Map<IServiceLocator, Extension> serviceLocators = new HashMap<IServiceLocator, Extension>();

	public ServiceRegistry() {
		log.info("ServiceRegistry created...");
	}

	@Override
	public void start(IExtensionRegistry extensionRegistry) {
		super.start(extensionRegistry);
		for (IServiceLocator sl : serviceLocators.keySet()) {
			sl.start();
		}
	}

	public void addExtension(Extension extension) {
		Class<?> clazz = extension.getClazz();

		String extensionType = extension.getPropertyValue(EXTENSION_TYPE).getStringValue();
		if (EXTENSION_TYPE_SERVICE.equals(extensionType)) {

			PropertyValue serviceInterfaceParam = extension.getPropertyValue(SERVICE_INTERFACE);
			if (serviceInterfaceParam == null || serviceInterfaceParam.getClassValue() == null) {
				throw new IllegalStateException(extension.getId() + " does not define parameter: " + SERVICE_INTERFACE);
			}
			Class<?> serviceInterface = serviceInterfaceParam.getClassValue();
			if (!serviceInterface.isAssignableFrom(extension.getClazz())) {
				throw new IllegalStateException("service: " + extension.getId() + " does not implement defined interface: "
						+ serviceInterfaceParam.getClassValue().getCanonicalName());
			}

			if (serviceInterface != null) {
				log.debug("registering service: " + extension.getId() + " binded to iface: " + serviceInterface.getCanonicalName());
				extensionsMap.put(serviceInterface, extension);
			}
		} else if (EXTENSION_TYPE_SERVICE_LOCATOR.equals(extensionType) || IServiceLocator.class.isAssignableFrom(extension.getClass())) {

			serviceLocators.put((IServiceLocator) extension.getExecutable(), extension);

		}
	}

	public void removeExtension(Extension extension) {
		String extensionType = extension.getPropertyValue(EXTENSION_TYPE).getStringValue();
		if (EXTENSION_TYPE_SERVICE_LOCATOR.equals(extensionType)) {

		} else if (EXTENSION_TYPE_SERVICE.equals(extensionType)) {

			Iterator<Entry<Class<?>, Extension>> iterator = extensionsMap.entrySet().iterator();
			while (iterator.hasNext()) {
				Entry<Class<?>, Extension> e = iterator.next();
				if (e.getValue() == extension) {
					Object service = servicesMap.remove(e.getKey());
					if (service != null)
						stopService(service);
					iterator.remove();
					return;
				}
			}
		}
	}

	@Override
	public void stop(IExtensionRegistry extensionRegistry) {
		super.stop(extensionRegistry);

		for (Entry<Class<?>, Object> e : servicesMap.entrySet()) {
			try {
				stopService(e.getValue());
			} catch (Exception ex) {
				log.error("", ex);
			}

		}
		for (Entry<IServiceLocator, Extension> e : serviceLocators.entrySet()) {
			e.getKey().stop();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.simplextensions.core.IIServiceRegistry#getService(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	public <T> T getService(Class<T> serviceInterface) {
		if (!isStarted())
			throw new IllegalStateException("Not yet started");

		Object service = servicesMap.get(serviceInterface);
		if (service == null) {
			Extension extension = extensionsMap.get(serviceInterface);
			if (extension != null) {
				service = extension.getExecutable();
				servicesMap.put(serviceInterface, service);
			}
		}
		if (service != null) {
			startService(service);
		} else {
			for (Entry<IServiceLocator, Extension> entry : serviceLocators.entrySet()) {
				T result = entry.getKey().getService(serviceInterface);
				if (result != null) {
					return result;
				}
			}
		}
		return (T) service;
	}

	private void stopService(Object service) {
		Class<? extends Object> serviceClass = service.getClass();
		do {
			for (Method m : serviceClass.getDeclaredMethods()) {
				Stop annotation = m.getAnnotation(Stop.class);
				if (annotation != null) {
					if (Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length > 0) {
						throw new RuntimeException("stop method must be public and have no arguments");
					}
					try {
						m.invoke(service);
						return;
					} catch (Exception e) {
						log.warn("couldn't stop service: " + service, e);
					}
				}
			}
			serviceClass = serviceClass.getSuperclass();
		} while (serviceClass != null);
	}

	private void startService(Object service) {
		Class<? extends Object> serviceClass = service.getClass();
		do {
			for (Method m : serviceClass.getDeclaredMethods()) {
				Start annotation = m.getAnnotation(Start.class);
				if (annotation != null) {
					if (Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length > 0) {
						throw new RuntimeException("start method must be public and have no arguments");
					}
					try {
						m.invoke(service);
						return;
					} catch (Exception e) {
						log.warn("couldn't start service: " + service, e );
					}
				}
			}
			serviceClass = serviceClass.getSuperclass();
		} while (serviceClass != null);
	}

}
